<?php

if (! defined('LIB_PATH'))
{
    die('The file ' . __FILE__ . ' needs to be required from the main executable of PhpPhoenix');
}

require_once LIB_PATH . 'PhpClass.php';
require_once LIB_PATH . 'PhpMethod.php';
require_once LIB_PATH . 'PhpConstant.php';
require_once LIB_PATH . 'PhpVariable.php';

/**
 * Parser 
 *
 * Class that represents the token's parser that groups
 * 
 * @package php-phoenix
 * @subpackage core
 * @version $id$
 * @author Pablo López Torres <pablolopeztorres@gmail.com> 
 */
class Parser
{
    /**
     * fetchUnknownElement
     * 
     * @param array $tokens 
     * @static
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @access public
     * @return PhpElement
     */
    public static function fetchUnknownElement(array &$tokens, $file = NULL, $projectName = NULL)
    {

        $token =& current($tokens);

        $element = NULL;

        switch ($token[0])
        {
            case T_FINAL:
            case T_ABSTRACT:
            case T_CLASS:
            case T_INTERFACE:

                $element = self::fetchClass($tokens, $file, $projectName);

                if (! isset($element))
                {
                    $element = self::fetchInterface($tokens, $file, $projectName);
                }

                break;
        }

        return $element;
    }
    /**
     * fetchClass
     * 
     * @param array $tokens 
     * @static
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @access public
     * @return PhpClass
     */
    public static function fetchClass(array &$tokens, $file = NULL, $project = NULL)
    {
        $token =& current($tokens);

        $isAClass = FALSE;
        $element = NULL;

        // get sign
        $sign = array();
        while ($token[0] !== T_USER_BRACE_OPEN)
        {
            if ($token[0] === T_CLASS)
            {
                $isAClass = TRUE;
            }

            $sign[] = $token;

            next($tokens);
            $token =& current($tokens);
        }

        if ($isAClass)
        {
            $element = new PhpClass($file, $project);

            if (! $element->fillFromHeader($sign))
            {
                throw new Exception("wrong source code, please run php -r {$argv[0]}");
            }

            if (! $element->fillFromSource($tokens))
            {
                throw new Exception("wrong source code, please run php -r {$argv[0]}");
            }
        }

        return $element;
    }
    /**
     * fetchInterface 
     * 
     * @param array $tokens 
     * @static
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @access public
     * @return PhpInterface
     */
    public static function fetchInterface(array &$tokens)
    {
    }
    /**
     * fetchMethod
     * 
     * @param array $tokens 
     * @param mixed $class 
     * @static
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @access public
     * @return PhpMethod
     */
    public static function fetchMethod(array &$tokens, $class = NULL) {
        // possible combinations
        // T_FUNCTION = (final)? (public|protected|private)? T_FUNCTION [method-name] (

        $token =& current($tokens);

        $isAMethod = FALSE;
        $methodAccess = 'public'; // should we use string for this case?
        $methodName = NULL;
        $methodParams = array();
        $methodContents = array(); // we'll use this to cover all variables inside the method
        $isFinal = FALSE;
        $isAbstract = FALSE;
        $isStatic = FALSE;

        // get header
        while ($token[0] !== T_USER_PARENTHESES_OPEN && $token[0] !== T_USER_SEMICOLON)
        {
            if ($token[0] === T_FUNCTION)
            {
                $isAMethod = TRUE;
            }

            $sign[] = $token;

            next($tokens);
            $token =& current($tokens);
        }

        $method = NULL;

        if ($isAMethod)
        {
            reset($sign);
        
            do
            {
                $token =& current($sign);

                switch ($token[0])
                {
                    case T_ABSTRACT:
                        $isAbstract = TRUE;
                        break;
                    case T_FINAL:
                        $isFinal = TRUE;
                        break;
                    case T_STATIC:
                        $isStatic = TRUE;
                        break;
                    case T_PRIVATE:
                        $methodAccess = 'private';
                        break;
                    case T_PROTECTED:
                        $methodAccess = 'protected';
                        break;
                    case T_PUBLIC:
                        $methodAccess = 'public';
                        break;
                    case T_FUNCTION:
                        $token =& next($sign);
                        if ($token[0] === T_USER_AMPERSAND) {
                            // @todo pablo - this should store that is a reference
                            $token =& next($sign);
                        }
                        $methodName = $token[1];
                        break;
                    default:
                        var_dump($token);
                        throw new Exception('unexpected token in method signature!!');
                        break;
                }

            }
            while (next($sign));

            // get parameters
            $params = array();
            $token =& current($tokens);

            // // get the tokens for the parameters
            $level = 0;
            if ($token[0] === T_USER_PARENTHESES_OPEN)
            {
                $level++;
                do
                {
                    next($tokens);
                    $token =& current($tokens);
                    
                    switch ($token[0])
                    {
                        case T_USER_PARENTHESES_OPEN:
                            $level++;
                            break;
                        case T_USER_PARENTHESES_CLOSE:
                            $level--;
                            break;
                    }

                    $params[] = $token;
                }
                while ($level > 0);
            }
            else
            {
                throw new Exception('the pointer of the array should be pointing to the first ( instance');
            }

            // parse parameters
            reset($params);

            // empty param
            $param = array(
                'reference' => FALSE,
                'name' => NULL,
                'type' => NULL,
                'default' => NULL,
            );

            do
            {
                $token =& current($params);

                switch ($token[0])
                {
                    case T_USER_AMPERSAND:
                        $param['reference'] = TRUE;
                        break;
                    case T_ARRAY:
                    case T_STRING:
                        $param['type'] = $token[1];
                        break;
                    case T_VARIABLE:
                        $param['name'] = $token[1];
                        break;
                    case T_USER_ASSIGNMENT:

                        $param['default'] = '';
                        // while not end of the parameter // commutative property is not applicable here ;)
                        while ($token =& next($params) && $token[0] !== T_USER_COMMA)
                        {
                            $param['default'] .= $token[1];
                        }

                        // return to the comma
                        $token =& prev($params);

                        break;
                    case T_USER_COMMA:
                        // store the before param
                        $methodParams[] = array(
                            'reference' => $param['reference'],
                            'name' => $param['name'],
                            'type' => $param['type'],
                            'default' => $param['default'],
                        );
                        // new param
                        $param = array(
                            'reference' => FALSE,
                            'name' => NULL,
                            'type' => NULL,
                            'default' => NULL,
                        );
                        break;
                    case T_USER_PARENTHESES_OPEN:
                    case T_USER_PARENTHESES_CLOSE:
                        // ignore them
                        break;
                    default:
                        var_dump(token_name($token[0]));
                        var_dump($token);
                        throw new Exception('error parsing the parameters section in the method');
                        break;
                }
            }
            while (next($params));

            if (isset($param['name']))
            {
                $methodParams[] = array(
                    'reference' => $param['reference'],
                    'name' => $param['name'],
                    'type' => $param['type'],
                    'default' => $param['default'],
                );
            
                if (! $isAbstract)
                {
                    // get body
                    $token = current($tokens);

                    // parse body
                    $level = 0;
                    if ($token[0] === T_USER_BRACE_OPEN)
                    {
                        do
                        {
                            switch ($token[0])
                            {
                                case T_USER_BRACE_OPEN:
                                    $level++;
                                    break;
                                case T_USER_BRACE_CLOSE:
                                    $level--;
                                    break;
                            }

                            next($tokens);
                            $token =& current($tokens);
                        }
                        while ($level > 0);
                    }
                }
            }

            if ($methodName !== NULL)
            {
                $method = array(
                    'is_abstract' => $isAbstract,
                    'is_final' => $isFinal,
                    'is_static' => $isStatic,
                    'access' => $methodAccess,
                    'name' => $methodName,
                    'params' => $methodParams,
                    'contents' => $methodContents,
                );
            }
        }


        return $method;
    }
    /**
     * fetchVariable 
     * 
     * @param array $tokens 
     * @param mixed $class 
     * @static
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @access public
     * @return void
     */
    public static function fetchVariable(array &$tokens, $class = NULL)
    {
        // @todo pablo - implement me!!
        return;
        $token =& current($tokens);

        $isAVar = FALSE;
        $varAccess = NULL; // should we use string for this case?
        $varName = NULL;
        $varValue = NULL;
        $isStatic = FALSE;

        // get header
        while ($token[0] !== T_USER_PARENTHESES_OPEN && $token[0] !== T_USER_SEMICOLON)
        {
            if ($token[0] === T_FUNCTION)
            {
                $isAMethod = TRUE;
            }

            $sign[] = $token;

            next($tokens);
            $token =& current($tokens);
        }

        if ($isAMethod)
        {
            reset($sign);
        
            do
            {
                $token =& current($sign);

                switch ($token[0])
                {
                    case T_ABSTRACT:
                        $isAbstract = TRUE;
                        break;
                    case T_FINAL:
                        $isFinal = TRUE;
                        break;
                    case T_STATIC:
                        $isStatic = TRUE;
                        break;
                    case T_PRIVATE:
                        $methodAccess = 'private';
                        break;
                    case T_PROTECTED:
                        $methodAccess = 'protected';
                        break;
                    case T_PUBLIC:
                        $methodAccess = 'public';
                        break;
                    case T_FUNCTION:
                        $token =& next($sign);
                        $methodName = $token[1];
                        break;
                    default:
                        var_dump($token);
                        throw new Exception('unexpected token in method signature!!');
                        break;
                }

            }
            while (next($sign));


            $token =& current($tokens);

            // get parameters
            $params = array();
            while ($token[0] !== T_USER_PARENTHESES_CLOSE)
            {
                $params[] = $token;

                next($tokens);
                $token =& current($tokens);
            }

            // parse parameters
            reset($params);

            // empty param
            $param = array(
                'name' => NULL,
                'type' => NULL,
                'default' => NULL,
            );

            do
            {
                $token =& current($params);

                switch ($token[0])
                {
                    case T_STRING:
                        $param['type'] = $token[1];
                        break;
                    case T_VARIABLE:
                        $param['name'] = $token[1];
                        break;
                    case T_USER_ASSIGNMENT:
                        $token =& next($params);
                        $param['default'] = $token[1];
                        break;
                    case T_USER_COMMA:
                        // store the before param
                        $methodParams[] = array(
                            'name' => $param['name'],
                            'type' => $param['type'],
                            'default' => $param['default'],
                        );
                        // new param
                        $param = array(
                            'name' => NULL,
                            'type' => NULL,
                            'default' => NULL,
                        );
                        break;
                    case T_USER_PARENTHESES_OPEN:
                    case T_USER_PARENTHESES_CLOSE:
                        // ignore them
                        break;
                    default:
                        var_dump(token_name($token[0]));
                        var_dump($token[1]);
                        throw new Exception('error parsing the parameters section in the method');
                        break;
                }
            }
            while (next($params));

            if (isset($param['name']))
            {
                $methodParams[] = array(
                    'name' => $param['name'],
                    'type' => $param['type'],
                    'default' => $param['default'],
                );
            }
            
            if (! $isAbstract)
            {
                // get body
                $token = current($tokens);

                // parse body
                $level = 0;
                if ($token[0] === T_USER_BRACE_OPEN)
                {
                    do
                    {
                        switch ($token[0])
                        {
                            case T_USER_BRACE_OPEN:
                                $level++;
                                break;
                            case T_USER_BRACE_CLOSE:
                                $level--;
                                break;
                        }

                        next($tokens);
                        $token =& current($tokens);
                    }
                    while ($level > 0);
                }
            }
        }

        $method = NULL;
        if ($isAMethod && $methodName !== NULL)
        {
            $method = array(
                'is_abstract' => $isAbstract,
                'is_final' => $isFinal,
                'is_static' => $isStatic,
                'access' => $methodAccess,
                'name' => $methodName,
                'params' => $methodParams,
                'contents' => $methodContents,
            );
        }

        return $method;
    }
    /**
     * fetchConstant 
     * 
     * @param array $tokens 
     * @param mixed $class 
     * @static
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @access public
     * @return void
     */
    public static function fetchConstant(array &$tokens, $class = NULL)
    {
    }

}

